package j_util.database;

import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*

    This file is part of Logica-Logike.

    Logica-Logike is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation version 3 of the License.

    Logica-Logike is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Logica-Logike.  If not, see <http://www.gnu.org/licenses/>.

 */
/**
 * This clas allows interact with the database, providing methods to create tables, drop them and add them information;
 */
public class DBConnection {
    private Connection connection;
    //The map uses statements String hashCode as Key, and a PreparedStatement as value.
    public Map<String, PreparedStatement> statements;
    public long savePointCounter;
    //reemplazar el PreparedStatement crudo por un objeto que contenga la hora en que fue usado por ultima vez
    //luego establecer una cola de prioridad por el usado mas recientemente con un piso de n PreparedStatements

    public DBConnection(Connection connection) {
        this.connection = connection;
        statements = new HashMap<String, PreparedStatement>();
    }

    public PreparedStatement createQueryStatement(String statement) throws SQLException {
        int replaceableParameters = 0;
        String preparedStatementString = statement;
        String[] array = preparedStatementString.split("'");
        for (int j = 0; j < array.length; j++) {
            preparedStatementString = preparedStatementString.replace("'" + array[j] + "'", "?");
            if (j % 2 == 1) {
                replaceableParameters++;
            }
        }
        if (!statements.containsKey(preparedStatementString)) {
            PreparedStatement preparedStatement = connection.prepareStatement(preparedStatementString);
            statements.put(preparedStatementString, preparedStatement);
        }
        for (int j = 1, parameter = 1; replaceableParameters > 0; j += 2, parameter++, replaceableParameters--) {
            switch (SQL_Library.getDataType(array[j])) {
                case BOOLEAN:
                    statements.get(preparedStatementString).setBoolean(parameter, Boolean.parseBoolean(array[j]));
                    break;
                case DOUBLE:
                    statements.get(preparedStatementString).setDouble(parameter, Double.parseDouble(array[j]));
                    break;
                case LONG:
                    statements.get(preparedStatementString).setLong(parameter, Long.parseLong(array[j]));
                    break;
                case STRING:
                    statements.get(preparedStatementString).setString(parameter, array[j]);
                    break;
            }
        }
        return statements.get(preparedStatementString);
    }

    /**
     * This method only executes the requested statement, commiting at the end;
     *
     * @param statementString - sql statement;
     * @throws SQLException;
     */
    public synchronized void executeUpdateStatement(String statementString) throws SQLException {
        connection.setAutoCommit(true);
        Statement statement = connection.createStatement();
        statement.executeUpdate(statementString);
    }

    public Runnable runnableExecuteUpdateStatement(final String statementString) {
        return new Runnable() {
            public void run() {
                try {
                    connection.setAutoCommit(true);
                    Statement statement = connection.createStatement();
                    statement.executeUpdate(statementString);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        };
    }

    public synchronized void executeUpdateStatement(String[] statementStrings) throws SQLException {
        connection.setAutoCommit(false);
        Statement statement = null;
        Savepoint sp = null;
//        try{
//            savePointCounter++;
//        }catch (Nu)
        try {
            sp = connection.setSavepoint("SAVE.POINT_" + savePointCounter);
        } catch (SQLException e) {
            //feature not supported on current DB

        }
        try {
            connection.setAutoCommit(false);
            statement = connection.createStatement();

            for (int j = 0; j < statementStrings.length; j++) {
                statement.executeUpdate(statementStrings[j]);
            }
            connection.commit();
        } catch (SQLException e) {
            if (sp != null) {
                connection.rollback(sp);
            } else {
                connection.rollback();
            }
        }
    }

    //to support thread pool
    public synchronized Runnable runnableExecuteUpdateStatement(final String[] statementStrings) throws SQLException {
        return new Runnable() {

            public void run() {
                try {
                    connection.setAutoCommit(false);
                    Statement statement = null;
                    Savepoint sp = null;
//        try{
//            savePointCounter++;
//        }catch (Nu)
                    try {
                        sp = connection.setSavepoint("SAVE.POINT_" + savePointCounter);
                    } catch (SQLException e) {
                        //feature not supported on current DB

                    }
                    try {
                        connection.setAutoCommit(false);
                        statement = connection.createStatement();

                        for (int j = 0; j < statementStrings.length; j++) {
                            statement.executeUpdate(statementStrings[j]);
                        }
                        connection.commit();
                    } catch (SQLException e) {
                        if (sp != null) {
                            connection.rollback(sp);
                        } else {
                            connection.rollback();
                        }
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }

            }
        };
    }

    public void dropAllTables() throws SQLException {
        List<TableInformation> tableInformationList = getAllTablesNames();
        String[] dropStatements = new String[tableInformationList.size()];
        for (int j = 0; j < tableInformationList.size(); j++) {
            dropStatements[j] = "drop table " + tableInformationList.get(j).getTableName();
        }
        executeUpdateStatement(dropStatements);
    }

    public synchronized Runnable runnableDropAllTables() throws SQLException {
        return new Runnable() {
            public void run() {
                List<TableInformation> tableInformationList = getAllTablesNames();
                String[] dropStatements = new String[tableInformationList.size()];
                for (int j = 0; j < tableInformationList.size(); j++) {
                    dropStatements[j] = "drop table " + tableInformationList.get(j).getTableName();
                }
                try {
                    executeUpdateStatement(dropStatements);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        };
    }

    public List<TableInformation> getAllTablesNames() {
        List<TableInformation> tableInformationList = new ArrayList<TableInformation>();
        try {
            // Gets the database metadata
            DatabaseMetaData dbmd;
            synchronized (connection) {//establezco lock para evitar uso concurrente
                dbmd = connection.getMetaData();
            }
            // Specify the type of object; in this case we want tables
            String[] types = {"TABLE"};
            ResultSet resultSet = dbmd.getTables(null, null, "%", types);

            TableInformation tableInformation;
            // Get the table names
            while (resultSet.next()) {
                tableInformation = new TableInformation();
                // Get the table name
                tableInformation.setTableName(resultSet.getString(3));
                // Get the table's catalog and schema names (if any)
                tableInformation.setTableCatalog(resultSet.getString(1));
                tableInformation.setTableSchema(resultSet.getString(2));
                tableInformationList.add(tableInformation);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return tableInformationList;
    }

    public List<String> getColumnNames(String tableName) throws SQLException {
        List<String> columnNameList = new ArrayList<String>();
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery("select * from " + tableName);
        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
        int numColumns = resultSetMetaData.getColumnCount();
        for (int j = 0; j < numColumns; j++) {
            columnNameList.add(resultSetMetaData.getColumnName(j + 1));
        }
        return columnNameList;
    }

    public Connection getConnection() {
        return connection;
    }
}

